const _ = require('lodash')
const database = require('../utils/database')
const parser = require('../parser/index')

let _parse = module.exports

_parse.gridWrap = (data, page, limit, count, ext = {}) => {
    let config = parser.getConfig()

    let last_page = Math.ceil(count / limit)

    if (page > last_page) page = last_page

    if (count === 0) page = 1

    return {
        [config['default_pager_rows_name'] || 'rows']: data,
        [config['default_pager_count_name'] || 'count']: count,
        [config['default_pager_current_name'] || 'current']: page,
        [config['default_pager_total_name'] || 'total']: last_page,
        [config['default_pager_limit_name'] || 'limit']: limit, ...ext
    }
}

/**
 * 获取一组数据
 * @param model     数据库模型
 * @param where     条件
 * @param page      开始位置
 * @param limit     分页大小
 * @param order     排序方式
 * @param include   join操作
 *         [
 *             {foreignKey:主表外键，targetKey:'链接表主键'，model: 链接表模型，where:查询条件，order：排序，attributes:属性，as:'别名'，relation:'one' or 'many' or 'has' , include:[子子查询]}
 *         ]
 * @param attributes
 * @returns {Promise<array>}
 */
_parse.getList = async (model, where = {}, page = 1, limit = 10, order = [], include = [], attributes = null) => {
    let config = parser.getConfig()
    let default_limit = (config['default_limit'] && parseInt(config['default_limit'])) || 10
    let default_limit_max = (config['default_limit_max'] && parseInt(config['default_limit_max'])) || 100

    where = where || {}
    page = (page && parseInt(page)) || 1
    limit = (limit && parseInt(limit)) || default_limit
    order = order || []
    include = include || []
    attributes = attributes || {}

    // limit 限制，防止分页过大
    if (limit > default_limit_max) limit = default_limit_max

    let offset = (page - 1) * limit

    let $model = model.split('@')
    let modelObj = database.M($model[0], $model[1])
    let dbObj = database.DB($model[1])

    if (!modelObj) throw new Error(`model not found : ${$model.join('@')}`)

    let ret
    if (dbObj.dialect === 'mongo') {
        ret = await modelObj.paginate(
            where,
            {
                select: attributes,
                sort: order,
                lean: true,
                leanWithId: false,
                offset: offset,
                limit: limit,
            }
        )

        await _parse.fillData(ret.docs, include)
        return _parse.gridWrap(ret.docs, page, limit, ret.total)
    } else {
        ret = await modelObj.findAndCountAll(
            {
                attributes: attributes,
                offset: offset,
                limit: limit,
                where: where,
                order: order,
                raw: true,
            }
        )

        await _parse.fillData(ret.rows, include)
        return _parse.gridWrap(ret.rows, page, limit, ret.count)
    }
}

/**
 * 获取一条数据
 * @param model     主模型
 * @param where     查询条件
 * @param order     排序
 * @param include   join操作
 *         [
 *             {foreignKey:主表外键，targetKey:'链接表主键'，model: 链接表模型，where:查询条件，order：排序，attributes:属性，as:'别名'，relation:'one' or 'many' or 'has', include:[子子查询]}
 *         ]
 * @param attributes
 * @returns {Promise<object>}
 */
_parse.getRow = async (model, where = {}, order = [], include = [], attributes = null) => {
    where = where || {}
    order = order || []
    include = include || []
    attributes = attributes || {}

    let $model = model.split('@')
    let modelObj = database.M($model[0], $model[1])
    let dbObj = database.DB($model[1])

    if (!modelObj) throw new Error(`model not found : ${$model.join('@')}`)

    let ret = dbObj.dialect === 'mongo' ? (
        await modelObj.findOne(where, attributes, {
            sort: order,
            lean: true,
        })
    ) : (
        await modelObj.findOne({
            attributes: attributes,
            where: where,
            order: order,
            raw: true,
        })
    )

    await _parse.fillData(ret, include)
    return ret
}

_parse.getAll = async (model, where = {}, order = [], include = [], attributes = null) => {
    where = where || {}
    order = order || []
    include = include || []
    attributes = attributes || {}

    let $model = model.split('@')
    let modelObj = database.M($model[0], $model[1])
    let dbObj = database.DB($model[1])

    if (!modelObj) throw new Error(`model not found : ${$model.join('@')}`)

    let ret = dbObj.dialect === 'mongo' ? (
        await modelObj.find(where, null, { sort: order }).select(attributes).lean()
    ) : (
        await modelObj.findAll(
            {
                attributes: attributes,
                where: where,
                order: order,
                raw: true,
            }
        )
    )

    await _parse.fillData(ret, include)
    return ret
}

_parse.fillData = async (rows, include = []) => {
    if (_.isPlainObject(include)) include = [include]

    let i = 0
    while (i < include.length) {
        let _cur = include[i]

        await _parse.fillRelationData(rows, _cur)

        if (_.isArray(_cur['$include'])) {
            await _parse.fillData(rows, _cur['$include'])
        }

        i++
    }
}

_parse._relationParse = (data, currentInclude, targetObjects) => {
    let relation = currentInclude['$relation'] || 'one'
    if (relation === 'one') {
        let _one_obj = targetObjects.find(f => f[currentInclude['$target']] == _.get(data, `${currentInclude['$foreign']}`))
        _.set(data, `${currentInclude.$as}`, _.keys(_one_obj).includes(`${currentInclude.$as}`) ? _.get(_one_obj, `${currentInclude.$as}`) : _one_obj)
    } else if (relation === 'many') {
        _.set(data, `${currentInclude.$as}`, targetObjects.filter(f => f[currentInclude['$target']] == _.get(data, `${currentInclude['$foreign']}`)))
    } else if (relation === 'has') {
        _.set(data, `${currentInclude.$as}`, !!~targetObjects.findIndex(f => f[currentInclude['$target']] == _.get(data, `${currentInclude['$foreign']}`)))
    } else if (relation === 'array') {
        let split = currentInclude['$split'] || ','
        let foreignKeys = _.get(data, `${currentInclude['$foreign']}`)

        if (!_.isArray(foreignKeys)) {foreignKeys = [foreignKeys]}

        foreignKeys = foreignKeys.map(m => {
            if (typeof m === 'string') {m = m.split(split)}
            return m
        }).reduce((acc, cur) => _.concat(acc, cur), [])

        // 去重，去假值
        foreignKeys = _.uniq(foreignKeys)

        _.set(data, `${currentInclude.$as}`, targetObjects.filter(f => {
            return !!~(foreignKeys || []).findIndex(fm => fm == f[currentInclude['$target']])
        }))
    }
}

// {*foreignKey:主表外键，*targetKey:'链接表主键'，*model: 链接表模型，*as:别名，*relation:'one' or 'many' or 'has' or 'array'，where:查询条件，order：排序，attributes:属性}
_parse.fillRelationData = async (data, currentInclude = {}) => {
    if (_.isEmpty(data)) return data
    if (!_.isObjectLike(data)) return data

    let relation = currentInclude['$relation'] || 'one'
    let where = currentInclude['$where'] || {}
    let order = currentInclude['$order'] || []
    let attributes = currentInclude['$attribute'] || {}
    let split = currentInclude['$split'] || ','

    // 提取外键并去重
    let foreignKeys
    if (_.isArray(data)) {
        foreignKeys = data.map(m => _.get(m, `${currentInclude['$foreign']}`))
    } else {
        foreignKeys = _.get(data, `${currentInclude['$foreign']}`)
        if (!_.isArray(foreignKeys)) {foreignKeys = [foreignKeys]}
    }

    if (relation === 'array') { //合并数组中的值
        foreignKeys = foreignKeys.map(m => {
            if (typeof m === 'string') {m = m.split(split)}
            return m
        }).reduce((acc, cur) => _.concat(acc, cur), [])
    }

    // 去重，去假值
    foreignKeys = _.uniq(foreignKeys)

    if (_.isEmpty(foreignKeys)) return false

    where[currentInclude['$target']] = { $in: foreignKeys }

    currentInclude.$model = currentInclude.$model.split('@')
    let model = database.M(currentInclude.$model[0], currentInclude.$model[1])
    if (!model) throw new Error(`model not found : ${currentInclude.$model.join('@')}`)
    currentInclude.$as = currentInclude.$as || `${currentInclude.$model[0]}`
    let dbObj = database.DB(currentInclude.$model[1])

    let targetObjects = dbObj.dialect === 'mongo' ? (
        await model.find(where, null, { sort: order }).select(attributes).lean()
    ) : (
        await model.findAll({
            where: where,
            order: order,
            attributes: attributes,
            raw: true,
        })
    )

    // relation 关系解析
    if (_.isArray(data)) {
        data.forEach(m => {
            _parse._relationParse(m, currentInclude, targetObjects)
        })
    } else {
        _parse._relationParse(data, currentInclude, targetObjects)
    }
}

_parse.add = async (model, params) => {
    let $model = model.split('@')
    let modelObj = database.M($model[0], $model[1])
    let dbObj = database.DB($model[1])

    if (!modelObj) throw new Error(`model not found : ${$model.join('@')}`)

    return dbObj.dialect === 'mongo' ? modelObj.create(params) : (
        _.isArray(params) ? modelObj.bulkCreate(params) : modelObj.create(params)
    )
}

_parse.update = async (model, where, params) => {
    let $model = model.split('@')
    let modelObj = database.M($model[0], $model[1])
    let dbObj = database.DB($model[1])

    if (!modelObj) throw new Error(`model not found : ${$model.join('@')}`)

    return dbObj.dialect === 'mongo' ? modelObj.update({ ...where }, { $set: { ...params } }) : modelObj.update({ ...params }, { where: { ...where } })
}

_parse.delete = async (model, where) => {
    let $model = model.split('@')
    let modelObj = database.M($model[0], $model[1])
    let dbObj = database.DB($model[1])

    if (!modelObj) throw new Error(`model not found : ${$model.join('@')}`)

    return dbObj.dialect === 'mongo' ? modelObj.remove({ ...where }) : modelObj.destroy({ where: { ...where } })
}

// 创建或者更新
_parse.createOrUpdate = async (model, where, params = {}) => {
    let _obj = await _parse.getRow(model, where)
    return _obj ? _parse.update(model, where, params) : _parse.add(model, params)
}
